/*
  S.M.A.C.K - An operating system kernel
  Copyright (C) 2010,2011 Mattias Holm and Kristian Rietveld
  For licensing and a full list of authors of the kernel, see the files
  COPYING and AUTHORS.
*/

#include <serial.h>
#include <device.h>
#include <vm.h>
#include <stddef.h>
#include <stdbool.h>
#include <process.h>
#include <interrupt.h>

union beagle_uart
{
  struct
    {
      uint8_t dll_reg;
      uint8_t dummy0[3];
      uint8_t dlh_reg;
      uint8_t dummy1[3];
      uint8_t iir_fcr_reg; /* IIR on read, FCR on write */
      uint8_t dummy2[3];
      uint8_t lcr_reg;
      uint8_t dummy3[3];
      uint8_t mcr_reg;
      uint8_t dummy4[3];
      uint8_t lsr_reg;
      uint8_t dummy5[3];
    }
  config_mode_a;
  struct
    {
      uint8_t dll_reg;
      uint8_t dummy0[3];
      uint8_t dlh_reg;
      uint8_t dummy1[3];
      uint8_t efr_reg;
      uint8_t dummy2[3];
      uint8_t lcr_reg;
      uint8_t dummy3[3];
      uint8_t xon1_addr1_reg;
      uint8_t dummy4[3];
      uint8_t xon2_addr2;
      uint8_t dummy5[3];
    }
  config_mode_b;
  struct
    {
      uint8_t rhr_thr_reg; /* RHR on read, THR on write */
      uint8_t dummy0[3];
      uint8_t ier_reg;
      uint8_t dummy1[3];
      uint8_t iir_fcr_reg; /* IIR on read, FCR on write */
      uint8_t dummy2[3];
      uint8_t lcr_reg;
      uint8_t dummy3[3];
      uint8_t mcr_reg;
      uint8_t dummy4[3];
      uint8_t lsr_reg;
      uint8_t dummy5[3];
      uint8_t msr_tcr_reg;
      uint8_t dummy6[3];
      uint8_t spr_tlr_reg;
      uint8_t dummy7[3];
      uint8_t mdr1_reg;
      uint8_t dummy8[3];
      uint8_t mdr2_reg;
      uint8_t dummy9[3];
      uint8_t sflsr_reg;
      uint8_t dummy10[3];
      uint8_t resume_reg;
      uint8_t dummy11[3];
      uint8_t sfregl_reg;
      uint8_t dummy12[3];
      uint8_t sfrergh_reg;
      uint8_t dummy13[3];
      uint8_t blr_reg;
      uint8_t dummy14[3];
      uint8_t acreg_reg;
      uint8_t dummy15[3];
      uint8_t scr_reg;
    }
  operational_mode;
};

#define UART3_IRQ 74

#define UART_RX_FIFO_E 0
#define UART_IER_RHR_IT 0
#define UART_IIR_IT_PENDING 0
#define UART_EFR_ENHANCED_EN 4
#define UART_TLR_TX_FIFO_TRIG_DMA 0

static volatile union beagle_uart *uart3 = NULL;

static proc_list_t uart_blocked = {NULL, NULL};

/* Async-safe input buffer */
#define INPUT_BUFFER_SIZE 256
static volatile struct input_buffer
{
  char buffer[INPUT_BUFFER_SIZE];
  uint32_t rd;
  uint32_t wr;
}
input_buffer =
{
  .rd = 0,
  .wr = 0
};

static bool
input_buffer_empty (void)
{
  return input_buffer.rd == input_buffer.wr;
}

static bool
input_buffer_full (void)
{
  return input_buffer.wr == (input_buffer.rd - 1) % INPUT_BUFFER_SIZE;
}

void
early_uart_tx_b (char ch)
{
  volatile union beagle_uart *uart = (union beagle_uart*)0x49020000;

  // Poll until TX fifo is empty
  while ((uart->operational_mode.lsr_reg & 0x40) == 0) ;

  uart->operational_mode.rhr_thr_reg = ch;
}

void
early_puts(const char *str)
{
  while (*str) {
    early_uart_tx_b(*str);
    str ++;
  }
}

void
early_putc(char c)
{
  early_uart_tx_b(c);
}


void
early_putx(uint32_t p)
{
  static const char hexlut[]
    = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

  early_uart_tx_b('0');
  early_uart_tx_b('x');

  for (int i = 28 ; i >= 0 ; i -= 4) {
    early_uart_tx_b(hexlut[((uintptr_t)p >> i) & 0xf]);
  }

}

void
early_putlf(void)
{
  early_uart_tx_b('\r');
  early_uart_tx_b('\n');
}


void
uart_tx_b(char ch)
{
  // Poll until TX fifo is empty
  while ((uart3->operational_mode.lsr_reg & 0x40) == 0) ;

  uart3->operational_mode.rhr_thr_reg = ch;
}



char
uart_rx_b(void)
{
  char ch;

  while (input_buffer_empty ())
    proc_block(&uart_blocked, proc_current());

  ch = input_buffer.buffer[input_buffer.rd];
  input_buffer.rd = (input_buffer.rd + 1) % INPUT_BUFFER_SIZE;

  return ch;
}

static void
uart_irq_handler(void *data)
{
  uint32_t iir;

  iir = uart3->operational_mode.iir_fcr_reg;

  /* If the bit is *not* set, an interrupt is pending */
  if ((iir & (1 << UART_IIR_IT_PENDING)) != 0)
    return;

  /* Read from fifo until empty.  This will clear the IRQ */
  while ((uart3->operational_mode.lsr_reg & (1 << UART_RX_FIFO_E)) == (1 << UART_RX_FIFO_E))
    {
      char ch;

      ch = uart3->operational_mode.rhr_thr_reg;

      if (input_buffer_full ())
        /* Drop data */
        continue;

      input_buffer.buffer[input_buffer.wr] = ch;
      input_buffer.wr = (input_buffer.wr + 1) % INPUT_BUFFER_SIZE;
    }

  /* We resume a single process, if this will not read from the
   * device, the interrupt will be asserted again.
   */
  if (!LIST_EMPTY(uart_blocked))
    proc_resume(&uart_blocked, LIST_FIRST(uart_blocked));
}

size_t
serial_read(dev_t *dev, dev_minor_t minor, void *buff, size_t nbytes)
{
  char *char_buff = buff;

  for (int i = 0; i < nbytes; i++)
    char_buff[i] = uart_rx_b();

  return nbytes;
}

size_t
serial_write(dev_t *dev, dev_minor_t minor, const void *buff, size_t nbytes)
{
  const char *char_buff = buff;

  for (int i = 0; i < nbytes; i++)
    {
      if (char_buff[i] == '\n')
        {
          uart_tx_b('\r');
          uart_tx_b('\n');
        }
      else
        uart_tx_b(char_buff[i]);
    }

  return nbytes;
}


int
serial_open(dev_t *dev, dev_minor_t minor)
{
  if (minor != 0)
    return -1;

  return 0;
}

int
serial_close(dev_t *dev, dev_minor_t minor)
{
  if (minor != 0)
    return -1;

  return 0;
}

static dev_ops_t serial_ops = {
  .read = serial_read,
  .write = serial_write,
  .seek = NULL,
  .open = serial_open,
  .close = serial_close,
  .ioctl = NULL,
};

static dev_t serial_dev = {
  .type = DEV_CHAR,
  .name = "serial",
  .mountname = "serial",
  .major = DEV_SERIAL,
  .minor_count = 1,
  .ops = &serial_ops,
};



void
serial_init(void)
{
  vm_map_t *vm = vm_get_kernel_map(VM_REG_DEVICE);

  uart3 = vm_map_physical(vm, VM_DEVICE | VM_SUPER_RW,
                          (uintptr_t)0x49020000, 4096);

  /* FIXME: Should do a proper initialization sequence here */

  /* Only enable RHR interrupt */
  uart3->operational_mode.ier_reg = 1 << UART_IER_RHR_IT;
  interrupt_register_irq_handler(UART3_IRQ, uart_irq_handler, NULL);
  unmask_interrupt(UART3_IRQ);

  dev_register(DEV_SERIAL, &serial_dev);
}
